查看原文
其他

C# 9.0 终于来了,您还学的动吗?

DotNet 2021-09-23

The following article is from 一线码农聊技术 Author 一线码农聊技术

(给DotNet加星标,提升.Net技能

转自:一线码农  
cnblogs.com/huangxincheng/p/13097256.html

一、背景


1、讲故事


好消息,.NET 5.0 终于在2020年6月10日发布了第五个预览版,眼尖的同学一定看到了在这个版本中终于支持了 C# 9.0,此处有掌声,太好了!!!


.Net5官方链接:https://dotnet.microsoft.com/download/dotnet/5.0



可以看到目前的C#9还是预览版,实现了一部分新语法供开发者提前尝鲜,从github的roslyn仓库上可以看到目前准备实现 17个新特性,现阶段已经实现了8个,其中的 In Progress 表示正在开发中。


新特性预览:https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md



2、安装必备


  • 下载最新的net5 sdk吧:dotnet-sdk-5.0.100-preview.5.20279.10-win-x64.exe


  • 下载最新的 visual studio 2019 preview 2:https://docs.microsoft.com/en-us/visualstudio/releases/2019/release-notes-preview



找好你自己的vs版本类型哦。


二、新特性研究


1、Target-typed new


这个取名一定要留给学易经的大师傅,没见过世面的我不敢造次,取得不佳影响时运,所谓 运去金成铁, 时来铁似金 ,不过大概意思就是说直接new你定义的局部变量的类型,用issues中总结的话就是:


Summary: Allow Point p = new (x, y);
Shipped in preview in 16.7p1.


接下来就是全部代码,看看使用前 和 使用后 的具体差别。


class Program
{
static void Main(string[] args)
{
//老语法
var person = new Person("mary", "123456");

//新语法
Person person2 = new("mary", "123456");
Console.WriteLine($"person={person}person2={person2}");
}
}
public class Person
{
private string username;
private string password;
public Person(string username, string password)
{
this.username = username;
this.password = password;
}
public override string ToString()
{
return $"username={username},password={password} \n";
}
}



然后用ilspy去看看下面的il代码,是不是省略了Person,让自己心里踏实一点。



总的来说这语法还行吧,能起到延长键盘使用寿命的功效。


2、Lambda discard parameters


从字面上看大概就是说可以在lambda上使用取消参数,听起来怪怪的,那本意是什么呢?有时候lambda上的匿名方法签名的参数是不需要的,但在以前必须实打实的定义,这样就会污染方法体,也就是可以在body中被访问,如下图:



但有时候因为客观原因必须使用Func<int,int,int>这样的委托,而且还不想让方法签名的参数污染方法体,我猜测在函数式编程中有这样的场景吧,可能有点类似MVC中的EmptyResult效果。


好了,我想你大概知道啥意思了,接下来实操一把。


Func<int, int, int> func = (_, _) =>{return 0;};
var result = func(10, 20);



从图中可以看到,我在方法体中是找不到所谓的 _ 变量的,这就神奇了,怎么做到的呢?带着这个好奇心看看它的IL代码是个什么样子。


.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2048
// Code size 45 (0x2d)
.maxstack 3
.entrypoint
.locals init (
[0] class [System.Runtime]System.Func`3<int32, int32, int32> func,
[1] int32 result
)
IL_0000: nop
IL_0001: ldsfld class [System.Runtime]System.Func`3<int32, int32, int32> ConsoleApp1.Program/'<>c'::'<>9__0_0'
IL_0006: dup
IL_0007: brtrue.s IL_0020

IL_0009: pop
IL_000a: ldsfld class ConsoleApp1.Program/'<>c' ConsoleApp1.Program/'<>c'::'<>9'
IL_000f: ldftn instance int32 ConsoleApp1.Program/'<>c'::'<Main>b__0_0'(int32, int32)
IL_0015: newobj instance void class [System.Runtime]System.Func`3<int32, int32, int32>::.ctor(object, native int)

IL_001a: dup
IL_001b: stsfld class [System.Runtime]System.Func`3<int32, int32, int32> ConsoleApp1.Program/'<>c'::'<>9__0_0'

IL_0020: stloc.0
IL_0021: ldloc.0
IL_0022: ldc.i4.s 10
IL_0024: ldc.i4.s 20
IL_0026: callvirt instance !2 class [System.Runtime]System.Func`3<int32, int32, int32>::Invoke(!0, !1)
IL_002b: stloc.1
IL_002c: ret
} // end of method Program::Main


从上面的IL代码来看 匿名方法 变成了<>c类的<Main>b__0_0方法,完整签名:ConsoleApp1.Program/'<>c'::'<Main>b__0_0'(int32, int32),然后再找一下 <Main>b__0_0 方法的定义。


.class nested private auto ansi sealed serializable beforefieldinit '<>c'
extends [System.Runtime]System.Object
.method assembly hidebysig
instance int32 '<Main>b__0_0' (
int32 _,
int32 _
) cil managed
{
// Method begins at RVA 0x2100
// Code size 7 (0x7)
.maxstack 1
.locals init (
[0] int32
)
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ldloc.0
IL_0006: ret
} // end of method '<>c'::'<Main>b__0_0'


这说明什么呢?说明两个参数是真实存在的,但编译器捣了鬼,做了语法上的限制,不让你访问所谓的 _。


等等。有一个问题,IL中的方法签名怎么是这样的:<Main>b__0_0 (int32 _,int32 _), 大家应该知道方法签名中不可以出现重复的参数名,比如下面这样定义肯定是报错的。



这说明什么?说明这个语法糖不仅需要编译器支持,更需要底层的JIT支持,那怎么证明呢?我们用windbg去底层挖一挖。。。为了方便调试,修改如下:


static void Main(string[] args)
{
Func<int, int, int> func = (_, _) =>
{
Console.WriteLine("进入方法体了!!!");
Console.ReadLine();
return 0;
};
var result = func(10, 20);
}

0:000> !clrstack -p
OS Thread Id: 0x52e8 (0)
0000007035F7E5C0 00007ffaff362655 ConsoleApp1.Program+c.b__0_0(Int32, Int32) [C:\5\ConsoleApp1\ConsoleApp1\Program.cs @ 13]
PARAMETERS:
this (0x0000007035F7E600) = 0x000001968000cb48
_ (0x0000007035F7E608) = 0x000000000000000a
_ (0x0000007035F7E610) = 0x0000000000000014



从图中可以看到,虽然都是 _ ,但在线程栈上是完完全全的两个栈地址。0x0000007035F7E608 和 0x0000007035F7E610。


三、总结


总的来说,C#是越来越向函数式编程靠拢,越来越像Scala,就像Jquery的口号一样:Write less,do more。


好了,先就说这两个吧,大家先安装好工具,明天继续解剖~~~


- EOF -



推荐阅读  点击标题可跳转
.NET 5.0 Preview 5 发布.NET Core实战之基于角色的访问控制的设计.NET Web应用中为什么要使用async/await异步编程


看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

好文章,我在看❤️

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存